package halma;

import java.util.Observable;


/*
 * HalmaBoard.java
 * 
 * 
 */

public class HalmaBoard extends Observable implements Board {
	private static HalmaBoard singleton;
	
	private int fieldWidth = 0;
	private int fieldHeight = 0;
	public Field field[][];
	boolean isValid = false; //???
	boolean isStartFound = false; //???
	private int boardType;
	
	static public HalmaBoard getInstance() {
		 if ( singleton == null )
			singleton = new HalmaBoard();
		 return singleton;
	}
	
	//Constructors
	public HalmaBoard() {
		boardType = HalmaSettings.BOARD_STARLIKE;
		fieldHeight = 36;
		fieldWidth = 25;
		field = new Field[fieldHeight][fieldWidth];
	}

	public HalmaBoard(int boardType) {
		//TODO
		//switch, groesse nach board type entsprechend setzen
		
		this.boardType = boardType;
		fieldHeight = 36;
		fieldWidth = 25;
		field = new Field[fieldHeight][fieldWidth];
	}
	
	
	//Getters and Setters
	public int getWidth() {
		return fieldWidth;
	}

	public int getHeight() {
		return fieldHeight;
	}

	public Field getField(int x, int y) {
		return field[x][y];
	}

	public void setField(int x, int y, Field field) {
		this.field[x][y] = field;
	}

	public Field[][] getField() {
		return field;
	}

	
	/*
	 * BUILD THE BOARD
	 * ~~~~~~~~~~~~~~~
	 * 
	 * Define the board as a two-dimensional array (n * n) 
	 * Field types are declared in HalmaSettings.
	 * 
	 *      
	 */
	
	public void createInitialField() {
		switch (this.boardType) {
		case HalmaSettings.BOARD_QUADRATICALLY:
			break;

		case HalmaSettings.BOARD_STARLIKE:
			//fieldHeight = 36;
			//fieldWidth = 25;
			//field = new Field[fieldHeight][fieldWidth];
			
			// black out everything
			for (int i = 0; i < fieldHeight; i++) {
				for (int j = 0; j < fieldWidth; j++) {
					// field[i][j]=-1;
					field[i][j] = new Field(-1, 0, 0, i, j);
				}
			}

			/*
			 * draw four triangles, mirror them * * * * * * * * *
			 * _______________ MIRROR
			 */

			// field
			int center = 13;
			for (int i = 10; i < 18; i++) {
				for (int j = center - ((i - 4) / 2); j < center + ((i - 2) / 2); j++) {
					field[i][j - 1] = new Field(0, 0, 0);
					field[34 - i][j - 1] = new Field(0, 0, 0);
					j++;
				}
				i++;
			}

			// players
			center = 13;
			for (int i = 0; i < 12; i++) {
				for (int j = center - (i / 2); j < center + (i / 2); j++) {
					// check for start- and end base
					switch (HalmaSettings.NUMBER_OF_PLAYERS) {
					case 2:
						field[i - 2][j] = new Field(0, 0, 0); // obere Spitze
						field[36 - i][j] = new Field(0, 0, 0); // untere Spitze
						j++;
						break;
					case 3:
						field[i - 2][j] = new Field(1, 1, 0);
						field[36 - i][j] = new Field(0, 0, 1);
						j++;
						break;
					case 4:
						field[i - 2][j] = new Field(0, 0, 0);
						field[36 - i][j] = new Field(0, 0, 0);
						j++;
						break;
					case 6:
						field[i - 2][j] = new Field(1, 1, 4);
						field[36 - i][j] = new Field(4, 4, 1);
						j++;
						break;
					}

				}
				i++;
			}
			center = 5;
			for (int i = 8; i < 20; i++) {
				for (int j = center - ((i - 8) / 2); j < center + ((i - 8) / 2); j++) {

					switch (HalmaSettings.NUMBER_OF_PLAYERS) {
					case 2:
						field[26 - i][j + 16] = new Field(2, 2, 0); // Ecke oben
						// rechts
						field[i + 8][j] = new Field(0, 0, 2); // Ecke unten
						// links
						field[26 - i][j] = new Field(1, 1, 0); // Ecke oben
						// links
						field[i + 8][j + 16] = new Field(0, 0, 1); // Ecke
						// unten
						// rechts
						j++;
						break;
					case 3:
						field[26 - i][j + 16] = new Field(3, 3, 0);
						field[i + 8][j] = new Field(0, 0, 3);
						field[26 - i][j] = new Field(2, 2, 0);
						field[i + 8][j + 16] = new Field(0, 0, 2);
						j++;
						break;
					case 4:
						field[26 - i][j + 16] = new Field(2, 2, 3);
						field[i + 8][j] = new Field(3, 3, 2);
						field[26 - i][j] = new Field(1, 1, 4);
						field[i + 8][j + 16] = new Field(4, 4, 1);
						j++;
						break;
					case 6:
						field[26 - i][j + 16] = new Field(2, 2, 5);
						field[i + 8][j] = new Field(5, 5, 2);
						field[26 - i][j] = new Field(6, 6, 3);
						field[i + 8][j + 16] = new Field(3, 3, 6);
						j++;
						break;
					}
				}
				i++;
			}

			for (int i = 0; i < 26; i++) {
				for (int j = 8; j < 18; j++) {
					field[8][j] = new Field(0, 0, 0);
					field[26][j] = new Field(0, 0, 0);
					j++;
				}
				i++;
			}
			field[10][7] = new Field(0, 0, 0);
			field[10][17] = new Field(0, 0, 0);
			field[12][6] = new Field(0, 0, 0);
			field[12][18] = new Field(0, 0, 0);
			field[14][5] = new Field(0, 0, 0);
			field[14][19] = new Field(0, 0, 0);
			field[16][4] = new Field(0, 0, 0);
			field[16][20] = new Field(0, 0, 0);
			field[18][4] = new Field(0, 0, 0);
			field[18][20] = new Field(0, 0, 0);
			field[24][7] = new Field(0, 0, 0);
			field[24][17] = new Field(0, 0, 0);
			field[22][6] = new Field(0, 0, 0);
			field[22][18] = new Field(0, 0, 0);
			field[20][5] = new Field(0, 0, 0);
			field[20][19] = new Field(0, 0, 0);
			for (int i = 18; i < 36; i++) {
				for (int j = 0; j < 25; j++) {
					field[i - 2][j] = field[i][j];
				}
				i++;
			}
			field[34][12] = new Field(-1, 0, 0);
			break;
		}

		// set field positions
		for (int i = 0; i < this.field.length; i++) {
			for (int j = 0; j < this.field[i].length; j++) {
				field[i][j].setXPosition(i);
				field[i][j].setYPosition(j);
			}
		}
		
		//now, notify observer
		setChanged();
        notifyObservers(this);
	}

	
	/* HELPERS
	 *
	 * Please add a description here.
	 * Please add a description above every method.
	 *
	 *
	 *
	 *
	*/
	public int getFieldInfo(int line, int column) {
		/*
		 * Gibt den FieldType als int zurueck (Uebergabe-Parameter sind
		 * Spielfeld-Koordinaten, keine realen Array-Koordinaten) Die
		 * Koordinaten sind jetzt folgendermassen zusammengesetzt: xPos: Die
		 * Mitte ist 0. Oberhalb davon wirds negativ, darunter dann positiv
		 * yPos: n-tes Element auf dem Spielfeld (beginnt bei 1)
		 */

		// mitte ist 16
		if (line == 0) {
			line = 16; // mitte
		} else {
			if (line < 0) { // oberhalb der mitte
				line = line * -1;
				line = 16 - (2 * line);
			} else { // unterhalb der mitte
				line = 16 + (2 * line);
			}
		}

		// n-tes Element aus line holen
		int tmp = 0, count = 0;
		for (int i = 0; i < fieldWidth; i++) {
			if (field[line][i].getFieldType() > -1) {
				count++;
				if (count == column)
					tmp = field[line][i].getFieldType();
			}
		}
		return (tmp);
	}

	// Methoden zum Konvertieren von der User-Eingabe zum internen
	public HalmaMove processCoordinates(HalmaMove in) {

		in.setFromXPosition(convertCoordinatesX(in.getFromXPosition()));
		in.setToXPosition(convertCoordinatesX(in.getToXPosition()));

		in.setFromYPosition(convertCoordinatesY(in.getFromXPosition(), in
				.getFromYPosition()));
		in.setToYPosition(convertCoordinatesY(in.getToXPosition(), in
				.getToYPosition()));

		return (in);
	}

	public int convertCoordinatesX(int line) {
		if (line == 0)
			line = 16;
		else {
			if (line < 0) {
				line = line * -1;
				line = 16 - (2 * line);

			} else {
				line = 16 + (2 * line);
			}
		}
		return (line);
	}

	public int convertCoordinatesY(int line, int column) {
		int count = 0;
		for (int i = 0; i < this.getWidth(); i++) {
			if (this.getField(line, i).getFieldType() > -1) {
				count++;
				if (count == column) {
					return (i);
				}
			}
		}
		return (-1);
	}

	public int xToUser(int x) {

		int userX = 0;

		// mitte ist 16
		if (x == 16) {
			userX = 0; // mitte
		} else {//
			userX = ((16 - x) / 2) * -1;

		}

		return userX;
	}

	public int yToUser(int y, int x) {
		int userY = 0;

		int counter = 0;
		boolean isVisited = false;
		// n-tes Element aus line holen
		for (int i = 0; i < fieldWidth; i++) {
			if (!isVisited) {
				counter++;
			}
			// erstes Feldelement gefunden
			if (field[x][i].getFieldType() > -1) {
				isVisited = true;
			}
		}
		userY = ((y - (counter - 1)) / 2) + 1;
		return userY;
	}
	
	
	//MOVE
	public void move(Player currentPlayer, Move move) {
		
		HalmaMove halmaMove = (HalmaMove) move;

		//move = this.processCoordinates(halmaMove);
		// now set the new positions
		Field tmp = this.getField(halmaMove.getFromXPosition(), halmaMove.getFromYPosition());

		tmp.setFieldType(0);
		this.setField(halmaMove.getFromXPosition(), halmaMove.getFromYPosition(), tmp);
		tmp = this.getField(halmaMove.getToXPosition(), halmaMove.getToYPosition());
		tmp.setFieldType(currentPlayer.getId() + 1);
		this.setField(halmaMove.getToXPosition(), halmaMove.getToYPosition(), tmp);
		
		setChanged();
        notifyObservers(this);
	}



	/* VALIDATION
	 *
	 * Please add a description here.
	 * Please add a description above every method.
	 *
	 *
	 *
	 *
	*/
	
	public void showField() {
		for(int i=0; i<field.length; i++) {
			for(int j=0; j<field[i].length; j++) {
				if (field[i][j].getFieldType() != -1) System.out.print(" "+field[i][j].getFieldType());
				else System.out.print("  ");
			}
			System.out.println();
		}
	}
	
	
    public boolean isMoveValid(Move move, Player player) {
    	HalmaMove halmaMove = (HalmaMove) move;

    	int fieldFromY = halmaMove.getFromYPosition();
    	int fieldFromX = halmaMove.getFromXPosition();
    	int fieldToY = halmaMove.getToYPosition();
    	int fieldToX = halmaMove.getToXPosition();
		
    	System.out.println("isMoveValid: fieldType="+field[fieldFromX][fieldFromY].getFieldType() + "->" + field[fieldToX][fieldToY].getFieldType());
    	
    	if (player.getId() + 1 != field[fieldFromX][fieldFromY].getFieldType()) 
    		return false;
    	
    	int targetFieldInfo = field[fieldToX][fieldToY].getFieldType();
				
    	if (targetFieldInfo != 0) 
    		return false;

    	Field[][] neighbors = getNeighbors(fieldToX,fieldToY);
	
    	boolean isSingleMove = false;
	
    	// Einzelsprung
    	for (int i = 0; i < neighbors.length; i++) {
    		for (int j = 0; j < neighbors[i].length; j++) {
    			Field field = (Field) neighbors[i][j];
    			if (field.getFieldType() != HalmaSettings.NO_FIELD) {
    				if (field.getXPosition() == fieldFromX && field.getYPosition() == fieldFromY) 
    				{ 
    					isSingleMove = true;
    					//return(true);
    				}
    			}			
    		}
    	}
						
    	// Wenn Mehrfachsprung, weiter suchen
    	if (!isSingleMove) {
    		if (checkJump(fieldFromX, fieldFromY,fieldToX, fieldToY)) return(true); else return(false);
    	} else if (targetFieldInfo > 0) {
    		return(false);
    	} else if (targetFieldInfo == HalmaSettings.NO_FIELD) return(false);
	
    	return true;
    }

    
	private Field[][] getNeighbors(int x, int y) {
		Field[][] neighbors = new Field[HalmaSettings.NEIGHBORHOOD_LENGTH][HalmaSettings.NEIGHBORHOOD_LENGTH];
		Field[][] fields = getField();
		
		int a = 0, b = 0;
		
		for (int i = x - 2; i <= x + 2; i++) {
			for (int j = y - 2; j <= y + 2; j++) {
				Field neighborField = new Field();

				if (i > 0 && j > 0 && i < (fields.length)
						&& j < (fields[i].length)) {
					Field arrField = fields[i][j];

					if (arrField.getFieldType() != HalmaSettings.NO_FIELD) {

						neighborField = arrField;
					}

				} else {
					neighborField.setFieldType(HalmaSettings.NO_FIELD);
					neighborField.setHomeBaseFromPlayer(0);
					neighborField.setEndBaseFromPlayer(0);
					neighborField.setXPosition(HalmaSettings.DUMMY_POSITION);
					neighborField.setYPosition(HalmaSettings.DUMMY_POSITION);
				}

				// neighbors[a][b] = fields[i][j];
				neighbors[a][b] = neighborField;
				b++;

			}
			a++;
			b = 0;
		}
		return neighbors;
	}
	
	private boolean isDirectionValid(int fieldStartX, int fieldStartY,int fieldTargetX, int fieldTargetY) {
		if (fieldTargetX == fieldStartX - 2 && fieldTargetY == fieldStartY - 3) {
			return false;
		} else if (fieldTargetX == fieldStartX + 2
				&& fieldTargetY == fieldStartY - 3) {
			return false;

		} else if (fieldTargetX == fieldStartX + 2
				&& fieldTargetY == fieldStartY + 3) {
			return false;
		} else if (fieldTargetX == fieldStartX - 2
				&& fieldTargetY == fieldStartY + 3) {
			return false;
		} else {
			return true;
		}
	}
	
	
	
    private boolean checkJump(int fieldStartX, int fieldStartY, int fieldTargetX, int fieldTargetY) {
		isValid = false;
		Field[][] neighbors = getNeighbors(fieldTargetX, fieldTargetY);

		for (int i = 0; i < neighbors.length; i++) 
		{
			for (int j = 0; j < neighbors[i].length; j++) 
			{
				Field field = neighbors[i][j];
				if (field.getFieldType() > 0) // neighbor occupied && != 0 ?
				{
					Field[][] neighbors2 = getNeighbors(field.getXPosition(), field.getYPosition());

					for (int p = 0; p < neighbors2.length; p++) // target position reached?
					{
						for (int q = 0; q < neighbors2[p].length; q++) 
						{

							Field field2 = neighbors2[p][q];

							if (field2.getYPosition() == fieldStartY&& field2.getXPosition() == fieldStartX) 
							{
								isStartFound = true;
								return isDirectionValid(fieldStartX,fieldStartY, fieldTargetX, fieldTargetY);
							} 
							else 
							{
								continue;
							}

						}
						
					}

				}
				
				
			}// for j
		}// for i
		
		
		// If target has not been reached, continue search
		if (!isStartFound) 
		{
			Field targetField = this.getField(fieldTargetX, fieldTargetY);
			
			if (targetField.getFieldType() == HalmaSettings.EMPTY_FIELD) 
			{
				Field[][] neighbors2 = getNeighbors(targetField.getXPosition(), targetField.getYPosition());

				for (int p = 0; p < neighbors2.length; p++) 
				{
					for (int q = 0; q < neighbors2[p].length; q++) 
					{
						Field field2 = neighbors2[p][q];

						if (field2.getFieldType() > HalmaSettings.EMPTY_FIELD) //field is not empty
						{
							Field[][] neighbors3 = getNeighbors(field2.getXPosition(), field2.getYPosition());

							for (int r = 0; r < neighbors3.length; r++) 
							{
								for (int s = 0; s < neighbors3[r].length; s++) 
								{

									Field field3 = neighbors3[r][s];

									if (field3.getFieldType() == HalmaSettings.EMPTY_FIELD) 
									{
										
										if (isDirectionValid(targetField.getXPosition(), 
												targetField.getYPosition(), 
												field3.getXPosition(), 
												field3.getYPosition())) {
											return checkJump(fieldStartX, fieldStartY,field3.getXPosition(),field3.getYPosition());
										}
									}
								}
							}

						} else { //field empty?

							continue;
						}

					}
				}

			}

		}

		if (isStartFound) {
			isValid = true;
		}

		return isValid;
	}

}
